package mqtt

import (
	"errors"
	"fmt"
	"lifi/plugins/common/tls"
	"net/url"
	"strings"
	"time"

	paho "github.com/eclipse/paho.mqtt.golang"
)

// mqtt v5-specific publish properties.
// See https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901109
type PublishProperties struct {
	ContentType    string            `yaml:"content_type" json:"contentType,omitempty"`
	ResponseTopic  string            `yaml:"response_topic" json:"responseTopic,omitempty"`
	MessageExpiry  time.Duration     `yaml:"message_expiry" json:"messageExpiry,omitempty"`
	TopicAlias     *uint16           `yaml:"topic_alias" json:"topicAlias,omitempty"`
	UserProperties map[string]string `yaml:"user_properties" json:"userProperties,omitempty"`
}

type MqttConfig struct {
	Servers  []string `yaml:"servers" json:"servers,omitempty"`
	Protocol string   `yaml:"protocol" json:"protocol,omitempty"`
	Username string   `yaml:"username" json:"username"`
	//Username            config.Secret      `yaml:"username" json:"username"`
	Password string `yaml:"password" json:"password"`
	//Password            config.Secret      `yaml:"password" json:"password"`
	Timeout             time.Duration      `yaml:"timeout" json:"timeout,omitempty"`
	ConnectionTimeout   time.Duration      `yaml:"connection_timeout" json:"connectionTimeout,omitempty"`
	QoS                 int                `yaml:"qos" json:"qoS,omitempty"`
	ClientID            string             `yaml:"client_id" json:"clientID,omitempty"`
	Retain              bool               `yaml:"retain" json:"retain,omitempty"`
	KeepAlive           int64              `yaml:"keep_alive" json:"keepAlive,omitempty"`
	PersistentSession   bool               `yaml:"persistent_session" json:"persistentSession,omitempty"`
	PublishPropertiesV5 *PublishProperties `yaml:"v5" json:"publishPropertiesV5,omitempty"`

	tls.ClientConfig `json:"tls.ClientConfig"`

	AutoReconnect    bool        `yaml:"-" json:"autoReconnect,omitempty"`
	OnConnectionLost func(error) `yaml:"-" json:"onConnectionLost,omitempty"`
}

// Client is a protocol neutral MQTT client for connecting,
// disconnecting, and publishing data to a topic.
// The protocol specific clients must implement this interface
type Client interface {
	Connect() (bool, error)
	Publish(topic string, data []byte) error
	SubscribeMultiple(filters map[string]byte, callback paho.MessageHandler) error
	AddRoute(topic string, callback paho.MessageHandler)
	Close() error
}

func NewClient(cfg *MqttConfig) (Client, error) {
	if len(cfg.Servers) == 0 {
		return nil, errors.New("no servers specified")
	}

	if cfg.PersistentSession && cfg.ClientID == "" {
		return nil, errors.New("persistent_session requires client_id")
	}

	if cfg.QoS > 2 || cfg.QoS < 0 {
		return nil, fmt.Errorf("invalid QoS value %d; must be 0, 1 or 2", cfg.QoS)
	}

	switch cfg.Protocol {
	case "", "3.1.1":
		return NewMQTTv311Client(cfg)
	case "5":
		return NewMQTTv5Client(cfg)
	}
	return nil, fmt.Errorf("unsuported protocol %q: must be \"3.1.1\" or \"5\"", cfg.Protocol)
}

func parseServers(servers []string) ([]*url.URL, error) {
	urls := make([]*url.URL, 0, len(servers))
	for _, svr := range servers {
		// Preserve support for host:port style servers; deprecated in Telegraf 1.4.4
		if !strings.Contains(svr, "://") {
			urls = append(urls, &url.URL{Scheme: "tcp", Host: svr})
			continue
		}

		u, err := url.Parse(svr)
		if err != nil {
			return nil, err
		}
		urls = append(urls, u)
	}
	return urls, nil
}
